#include <stx/test/test.hpp>

TEST_CASE (core_stx)
{
    {
        auto mv = MemView(MemSpan{});
        mv      = MemSpan{};
    }

    auto fn_test_pod_buf = []<IsNonVoid T>
    {
        using Value_ = typename T::value_type;
        auto arr1    = std::array<char, 1024>{};
        auto arr2    = std::array<char, 1024>{};

        randomize(arr1);
        randomize(arr2);
        CHECK(arr1 != arr2);

        auto p1 = new (arr1.data()) T(32);
        auto p2 = new (arr2.data()) T(32);

        CHECK(p1->size() == 32);
        CHECK(p1->size() == p1->capacity());

        randomize(*p1);
        randomize(*p2);
        CHECK(*p1 != *p2);

        *p1 = *p2;
        CHECK(*p1 == *p2);
        auto b3 = std::move(*p2);
        CHECK(b3 == *p1);
        CHECK(*p1 != *p2);
        CHECK(p2->empty());

        p2->assign(p1->begin(), p1->end());
        CHECK(*p1 == *p2);
        CHECK(p1->size() == p2->size());
        CHECK(p1->size() == p1->capacity());

        p2->assign(23, Value_{});
        CHECK(p2->size() == 23);
        CHECK(ranges::all_of(*p2,
                             [](auto const& e)
                             { return MemView(e) == MemView(Value_{}); }));

        p2->reserve(178);
        CHECK(p2->capacity() == 178);
        CHECK(p2->size() == 23);
        CHECK(ranges::all_of(*p2,
                             [](auto const& e)
                             { return MemView(e) == MemView(Value_{}); }));

        p2->reserve(58);
        CHECK(p2->capacity() == 178);
        CHECK(p2->size() == 23);
        CHECK(ranges::all_of(*p2,
                             [](auto const& e)
                             { return MemView(e) == MemView(Value_{}); }));

        p2->clear();
        CHECK(p2->capacity() == 178);
        CHECK(p2->empty());
        CHECK(p2->size() == 0);

        p2->shrink_to_fit();
        CHECK(p2->capacity() == 0);
        CHECK(p2->empty());
        CHECK(p2->size() == 0);

        p2->push_back(Value_{});
        CHECK(p2->capacity() > 0);
        CHECK(!p2->empty());
        CHECK(p2->size() == 1);

        p2->emplace_back(Value_{});
        CHECK(p2->capacity() > 0);
        CHECK(!p2->empty());
        CHECK(p2->size() == 2);

        p2->resize(234);
        CHECK(!p2->empty());
        CHECK(p2->size() == 234);

        p2->resize(17, Value_{});
        CHECK(!p2->empty());
        CHECK(p2->size() == 17);

        auto b1 = PodBuffer<int>(23);
        CHECK(b1.size() == 23);
        CHECK(b1.size() == b1.capacity());

        b1 = PodBuffer<int>{1, 3, 5, 7};
        CHECK(b1.size() == 4);
        CHECK(ranges::equal(b1, std::vector{1, 3, 5, 7}));

        b1 = PodBuffer<int>(5, 0xac);
        CHECK(b1.size() == 5);
        CHECK(b1.size() == b1.capacity());
        CHECK(ranges::equal(b1, std::vector{0xac, 0xac, 0xac, 0xac, 0xac}));

        b1.emplace(b1.begin() + 1, 23);
        CHECK(b1.size() == 6);
        CHECK(b1.size() < b1.capacity());
        CHECK(ranges::equal(b1, std::vector{0xac, 23, 0xac, 0xac, 0xac, 0xac}));

        b1.erase(b1.begin() + 1);
        CHECK(b1.size() == 5);
        CHECK(b1.size() < b1.capacity());
        CHECK(ranges::equal(b1, std::vector{0xac, 0xac, 0xac, 0xac, 0xac}));

        b1.erase(b1.begin() + 1, b1.begin() + 4);
        CHECK(b1.size() == 2);
        CHECK(b1.size() < b1.capacity());
        CHECK(ranges::equal(b1, std::vector{0xac, 0xac}));

        b1.insert(b1.begin() + 1, 7);
        CHECK(b1.size() == 3);
        CHECK(ranges::equal(b1, std::vector{0xac, 7, 0xac}));

        b1.insert(b1.begin() + 1, {0xac, 3, 0xac});
        CHECK(b1.size() == 6);
        CHECK(ranges::equal(b1, std::vector{0xac, 0xac, 3, 0xac, 7, 0xac}));

        b1.insert(b1.begin() + 1, 3, 7);
        CHECK(b1.size() == 9);
        CHECK(ranges::equal(
            b1, std::vector{0xac, 7, 7, 7, 0xac, 3, 0xac, 7, 0xac}));
    };

    fn_test_pod_buf.template operator()<Buffer>();
    fn_test_pod_buf.template operator()<PodBuffer<::sockaddr>>();

    auto opt_int = std::make_optional(2);
    auto fn_n2s  = [](int n) { return tos(n * 2); };
    auto fn_s2n  = [](std::string s) { return s.size() * 2; };
    auto opt_r   = opt_int | fn_n2s | fn_s2n;

    CHECK(opt_r && 2 == *opt_r);

    opt_r = opt_int |
            [](int n)
    {
        if (n > 0)
        {
            RUNTIME_ERROR();
        }

        return std::string{};
    } | fn_s2n;

    CHECK(!opt_r && gLastError().isFailed());

    auto b1 = Buffer(128);
    randomize(b1);
    auto b2 = b1;
    CHECK(b1 == b2);
    auto b3 = std::move(b1);
    CHECK(b1.empty());
    CHECK(b1.size() == 0);
    CHECK(b2 == b3);
    CHECK(b1 != b2);
    CHECK(b1 != b3);

    static_assert(min(123_u8) == 0);
    static_assert(min(123_u8) == std::numeric_limits<std::uint8_t>::min());
    static_assert(max(123_u8) == 255);
    static_assert(max(123_u8) == std::numeric_limits<std::uint8_t>::max());

    static_assert(min<std::uint8_t>() == 0);
    static_assert(min<std::uint8_t>() ==
                  std::numeric_limits<std::uint8_t>::min());
    static_assert(max<std::uint8_t>() == 255);
    static_assert(max<std::uint8_t>() ==
                  std::numeric_limits<std::uint8_t>::max());

    CHECK(std::byteswap(0xabcd_u16) == 0xcdab_u16);
    CHECK(std::byteswap(0x1234abcd_u32) == 0xcdab3412_u32);

    static_assert(isHexChar('0'));
    static_assert(isHexChar('9'));
    static_assert(isHexChar('a'));
    static_assert(isHexChar('A'));
    static_assert(isHexChar('f'));
    static_assert(isHexChar('F'));
    static_assert(!isHexChar('g'));
    static_assert(!isHexChar('G'));
    static_assert(!isHexChar('%'));
    static_assert(!isHexChar('@'));
    static_assert(!isHexChar('~'));

    static_assert(isAlphanumeric('0'));
    static_assert(isAlphanumeric('9'));
    static_assert(isAlphanumeric('a'));
    static_assert(isAlphanumeric('A'));
    static_assert(isAlphanumeric('z'));
    static_assert(isAlphanumeric('Z'));
    static_assert(!isAlphanumeric('%'));
    static_assert(!isAlphanumeric('@'));
    static_assert(!isAlphanumeric('~'));

    static_assert(IsNumberLike<int>);
    static_assert(IsNumberLike<double>);
    static_assert(IsNumberLike<bool>);
    static_assert(IsNumberLike<MinWidth>);
    static_assert(IsNumberLike<Number>);
    static_assert(!IsNumberLike<std::byte>);
    static_assert(!IsNumberLike<Tco>);

    CHECK(Number(-1) < 0uz);
    CHECK(Number(-1) < 1uz);
    CHECK(Number(-1) == -1z);
    CHECK(Number(3.14159) > 3.14158);
    CHECK(Number(3.14159) != 3.14);

    CHECK(max<std::uint8_t>() == std::uint8_t(Number(257)));
    CHECK(min<std::int8_t>() == std::int8_t(Number(-257)));
    CHECK(min(23_i32) == min(257_i32));
    CHECK(min(-23_i32) == min(-257_i32));
    CHECK(min(-23_i32) == min(257_i32));
    CHECK(min(23_i32) == min(-257_i32));
    CHECK(max(23_i32) == max(257_i32));
    CHECK(max(-23_i32) == max(-257_i32));
    CHECK(max(-23_i32) == max(257_i32));
    CHECK(max(23_i32) == max(-257_i32));

    CHECK(getUniqueId() != getUniqueId());
    CHECK(ton(Tco::K_BOLD) == 1);
    CHECK(*ton("123"sv) == 123);
    CHECK(*ton("1234abcd"sv, NumberBase::K_16) == 0x1234abcd);

    {
        auto v         = std::vector<int>{1, 2, 3};
        auto [p, size] = getMemAreaInfo(v);
        CHECK(bin2hex(v.data()) == bin2hex(p));
        CHECK(v.size() == size / sizeof(int));

        auto mv          = MemView(v);
        auto [p2, size2] = getMemAreaInfo(mv);
        CHECK(bin2hex(v.data()) == bin2hex(p2));
        CHECK(v.size() == size2 / sizeof(int));

        auto ms          = MemSpan(v);
        auto [p3, size3] = getMemAreaInfo(ms);
        CHECK(bin2hex(v.data()) == bin2hex(p3));
        CHECK(v.size() == size3 / sizeof(int));
    }

    CHECK('b' == getLowHalfHexChar(0xab_u8, SbLowercase{}));
    CHECK('a' == getHighHalfHexChar(0xab_u8, SbLowercase{}));

    {
        auto n = 0_u32;
        CHECK(4 == hex2bin("1234abcd"sv, &n, sizeof(n)));
        CHECK(0xcdab3412 == n);
    }

    {
        auto n = 0xcdab3412_u32;
        CHECK(0 == std::memcmp(hex2bin("1234abcd"sv).data(), &n, 4));
    }

    {
        auto n = 0xcdab3412_u32;
        CHECK("1234abcd"sv == bin2hex(&n, sizeof(n), SbLowercase{}));
    }

    {
        auto n = 0xcdab3412_u32;
        CHECK("1234abcd"sv == bin2hex(&n, sizeof(n)));
    }

    {
        auto n = 0xcdab3412_u32;
        CHECK("1234ABCD"sv == bin2hex(&n, sizeof(n), SbLowercase(false)));
    }

    {
        auto v = std::vector<std::uint8_t>{1, 2, 3, 4, 0xab, 0xcd};
        auto const [p, size] = getMemAreaInfo(v);
        CHECK("01020304abcd"sv == bin2hex(p, size, SbLowercase{}));
    }

    {
        auto v = std::vector<std::uint8_t>{1, 2, 3, 4, 0xab, 0xcd};
        auto const [p, size] = getMemAreaInfo(v);
        CHECK("01020304ABCD"sv == bin2hex(p, size, SbLowercase(false)));
    }

    {
        auto v = std::vector<std::uint8_t>{1, 2, 3, 4, 0xab, 0xcd};
        auto const [p, size] = getMemAreaInfo(v);
        CHECK("01020304abcd"sv == bin2hex(p, size));
    }

    {
        auto v = std::vector<std::uint8_t>{1, 2, 3, 4, 0xab, 0xcd};
        zero(v);
        auto const [p, size] = getMemAreaInfo(v);
        CHECK("000000000000"sv == bin2hex(p, size));
    }

    {
        auto ba = ByteArray<4>("\x1\x2\x3\x4"sv.data());
        CHECK(ba.size() == 4);
        CHECK(std::byte(1) == ba[0]);
        CHECK(std::byte(2) == ba[1]);
        CHECK(std::byte(3) == ba[2]);
        CHECK(std::byte(4) == ba[3]);
    }

    {
        auto atomic_ints = Atomic<std::list<int>>{};

        auto t1 = std::thread(
            [&]
            {
                LOOP (i, 10_K)
                {
                    atomic_ints.visit(
                        [=](auto& ints)
                        {
                            insert(ints, (i + 1) * 2);
                            insert(ints, (i + 1) * 2);
                        });
                }
            });

        auto t2 = std::thread(
            [&]
            {
                LOOP (i, 10_K)
                {
                    atomic_ints.visit(
                        [=](auto& ints)
                        {
                            insert(ints, (i + 1) * 2 + 1);
                            insert(ints, (i + 1) * 2 + 1);
                        });
                }
            });

        t1.join();
        t2.join();

        auto ints = atomic_ints.visit([](auto& ints)
                                      { return std::exchange(ints, {}); });

        for (auto pos = ints.begin(); pos != ints.end(); ++pos, ++pos)
        {
            auto next_pos = pos;
            ++next_pos;

            if (*pos % 2)
            {
                CHECK(*next_pos % 2);
                continue;
            }

            CHECK(0 == *next_pos % 2);
        }
    }

    {
        auto dict      = Map<std::string, int, CiLess>{};
        dict["Hello"s] = 1;
        dict["hello"s] = 2;

        CHECK(dict.size() == 1);
        CHECK(dict.begin()->first == "Hello"sv);
        CHECK(contains(dict, "HELLO"sv));
        CHECK(contains(dict, "HeLLo"sv));
    }

    {
        auto dict =
            UnorderedMap<std::string, int, Hash<std::string>, CiEqualTo>{};
        dict["Hello"s] = 1;
        dict["hello"s] = 2;

        CHECK(dict.size() == 2);
        CHECK(!contains(dict, "HELLO"sv));
        CHECK(!contains(dict, "HeLLo"sv));
    }

    {
        auto dict      = UnorderedMap<std::string, int, CiHash, CiEqualTo>{};
        dict["Hello"s] = 1;
        dict["hello"s] = 2;

        CHECK(dict.size() == 1);
        CHECK(dict.begin()->first == "Hello"sv);
        CHECK(contains(dict, "HELLO"sv));
        CHECK(contains(dict, "HeLLo"sv));
    }
}